package com.lizonghui.googleplay.base;

import android.support.annotation.NonNull;

import com.google.gson.Gson;
import com.lizonghui.googleplay.config.Constants;
import com.lizonghui.googleplay.utils.FileUtils;
import com.lizonghui.googleplay.utils.HttpUtils;
import com.lizonghui.googleplay.utils.IOUtils;
import com.lizonghui.googleplay.utils.LogUtils;
import com.lizonghui.googleplay.utils.UIUtils;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2016/6/21.
 * 对协议请求类的基类抽取
 * 1:由于返回类型不确定，就定义为泛型
 * 2.自定义缓存关键字，最为协议缓存的唯一标识
 * 3:对协议缓存的优化。取：先内容，后本地，再网络。 在网络后存到内存和本地，本地获取后存到内容
 */
public abstract class BaseProtocol<T> {
    /**
     * @param index 分页请求的索引
     * @return 返回一个解析后的数据类型，因为我们不知道是什么类型用T泛型
     * @throws IOException
     */
    public T loadData(int index) throws IOException {
        //1从内存加载-->我们存到全局量中
        MyApplication app = (MyApplication) UIUtils.getContext();
        Map<String, String> protocolCacheMap = app.getProtocolCacheMap();

        //得到缓存关键字 getInterfaceKey() + "." + value
        String key = generateKey(index);

        T t = null;

        //如果内存有，就从内存取出来
        if (protocolCacheMap.containsKey(key)) {
            LogUtils.i("BaseProtocol", "从内存加载数据-->" + key);

            String memJsonString = protocolCacheMap.get(key);
            t = parseJsonString(memJsonString);
            return t;
        }

        //内存没有就从本地加载.本地文件也是自己创建的，路径定义是：dir+key
        t = loadDataFromLocal(key);
        if (t != null) {//本地有数据，存内存，再返回
            LogUtils.i("BaseProtocol", "从本地加载数据-->" + getCacheFile(key).getAbsolutePath());
            return t;
        }

        //本地还没有才从网络获取，网络解析回来之后要去除空格。第一行写入时间，第二行才写json内容
        t = loadDataFromNet(index);
        if (t != null) {
            return t;
        } else {
            return null;
        }
    }

    /**
     * 从本地加载协议内容，用读取流读取到数据，之后存到内存，再返回
     *
     * @param key
     * @return
     */
    private T loadDataFromLocal(String key) {
        File cacheFile = getCacheFile(key);//通过key拿到存放JSON数据的文本对象

        if (cacheFile.exists()) {//有缓存
            //读取第一行，判断是否过期
            BufferedReader reader = null;//为了关闭要在这里先定义空
            try {
                //用读取流来读取文件内容
                reader = new BufferedReader(new FileReader(cacheFile));
                String insertTimeStr = reader.readLine();//读取第一行，其实是缓存插入时间
                long insertTime = Long.parseLong(insertTimeStr);//将时间转换为long类型

                if (System.currentTimeMillis() - insertTime < Constants.PROTOCOLTIMEOUT) {
                    //没有过期，则读取缓存内容，其实是读取下一行
                    String cacheJsonString = reader.readLine();

                    //把本地数据存到内存
                    MyApplication app = (MyApplication) UIUtils.getContext();
                    app.getProtocolCacheMap().put(key, cacheJsonString);

                    //返回解析
                    return parseJsonString(cacheJsonString);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                IOUtils.close(reader);
            }
        }
        return null;
    }

    /**
     * 得到Json的缓存文件对象
     *
     * @param key 缓存关键字
     * @return
     */
    @NonNull
    private File getCacheFile(String key) {
        //文件是否存在
        String dir = FileUtils.getDir("json");//我们定义的方法获取路径：sdcard/android/data/包目录/json
        return new File(dir, key);//创建一个文件对象：dir+key（地址+命名）
    }

    /**
     * 我们定义缓存关键字格式为： getInterfaceKey() + "." + value 例如：home.0 或者 packageName.com.itheima.www
     * 当遇到detail页面时候，申请协议的key是packageName，这里要修改。
     * * @param index
     *
     * @return
     */
    private String generateKey(int index) {
        Map<String, Object> parmasHashMap = getParmasHashMap(index);
        //tab-->index,0  index 20,  index 40;
        //detail->packageName 包名1，packageName 包名2
        for (Map.Entry<String, Object> info : parmasHashMap.entrySet()) {
            String key = info.getKey();//“home”,"game","packageName"
            Object value = info.getValue();//0 20 40  包名1 包名2 包名3
            return getInterfaceKey() + "." + value;
        }
        return null;
    }

    /**
     * 从网络加载数据,响应之后要存到内存和存到本地
     *
     * @param index
     * @return
     * @throws IOException
     */
    private T loadDataFromNet(int index) throws IOException {
        LogUtils.i("BaseProtocol", "从网络加载数据");
          /*-------------------1请求网络得到jsonString-----------*/
        //使用okHttp请求网络数据
        //创建okHttpClient实例对象
        OkHttpClient okHttpClient = new OkHttpClient();

        String url = Constants.URLS.BASEURL + getInterfaceKey() + "?";//1构造方法传递进来2参数方式传递进来3暴露一个方法
        //参数
        Map<String, Object> parmasMap = getParmasHashMap(index);
//        parmasMap.put("packageName", "" + packageName);
        String urlParamsByMap = HttpUtils.getUrlParamsByMap(parmasMap);//Map对象，转成字符串
        //dir=E:\\GooglePlay\\googlePlay_day01\\资料\\服务器
        //http://192.168.0.83:8080/GooglePlayServer/home?index=1
        //拼接之后的结果
        url = url + urlParamsByMap;

        //创建一个请求对象
        Request request = new Request.Builder().get().url(url).build();

        //发起同步请求,因为这里已经是子线程，所以用同步请求
        Response response = okHttpClient.newCall(request).execute();

        if (response.isSuccessful()) {//响应成功
//            String resultJsonString = response.body().string();

            InputStream inputStream = response.body().byteStream();
            BufferedReader bfr = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = bfr.readLine()) != null) {
                sb.append(line);
            }
            bfr.close();
            String resultJsonString = sb.toString();//这样子可以去除协议中的空格，使得后面写入时候不会有空格，导致写入不完整

            LogUtils.i("BaseProtocol", "响应成功" + resultJsonString);
            /*------------响应之后，要把数据存内存----------*/
            LogUtils.i("BaseProtocol", "写入缓存到内存");
            MyApplication app = (MyApplication) UIUtils.getContext();
            String key = generateKey(index);
            app.getProtocolCacheMap().put(key, resultJsonString);

            /*------------响应之后，要把数据存本地----------*/
            /*------------存本地，考虑两个方面，1放到哪里2文件命名（保证唯一性）----------*/
            File cacheFile = getCacheFile(key);
            BufferedWriter writer = null;
            try {
                LogUtils.i("BaseProtocol", "写入内存到本地");
                writer = new BufferedWriter(new FileWriter(cacheFile));
                //写入第一行-->插入时间r
                writer.write(System.currentTimeMillis() + "");
                //换行
                writer.newLine();
                //写入第二行,就是解析内容。
                //因为返回的内容不单单是一行，而我们又只写了一行，就当成全部JsonString给存到本地去了，
                // 导致之后再申请读取的数据时候，本地协议缓存就出错了。因为不完整数据就解析不出来了
                //学会打断点去分析流程，分析出错的思路
                //解决办法1在之类写入时候把空格和换行符号置换2在body出获取输入流，再写入
                writer.write(resultJsonString);//这个写法当遇到换行时候会停止
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                IOUtils.close(writer);
            }

            /*-------------------2解析得到jsonString-----------*/
            T t = parseJsonString(resultJsonString);
            return t;
        } else {
            return null;
        }

    }

    /**
     * @param index
     * @return
     * @des 得到地址字段
     * @des 当需要地址的字段不是默认时候，子类可以去复写
     */
    public Map<String, Object> getParmasHashMap(int index) {
        Map<String, Object> defaultParamsHashMap = new HashMap<>();
        defaultParamsHashMap.put("index", "" + index);
        return defaultParamsHashMap;//默认返回index=index;
    }

    /**
     * @param resultJsonString
     * @return
     * @des 解析网络返回的内容JsonString
     */
//    public abstract T parseJsonString(String resultJsonString);


    //泛型解析时候，通过java反射机制，获得子类实现的泛型参数类型，并解析
    public T parseJsonString(String resultJsonString) {
        Gson gson = new Gson();
//        Class c = this.getClass();//得到子类的类型
//        Type type = c.getGenericSuperclass();//得到子类传递的完整参数化类型
//        ParameterizedType pType = (ParameterizedType) type;//还需要转换为参数化类型
//        Type[] types = pType.getActualTypeArguments();//得到真的类型参数们
//        Type actualType = types[0];//得到第一个类型参数
        Type actualType = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

        Object o = gson.fromJson(resultJsonString, actualType);
        return (T) o;
    }

    /**
     * @return
     * @des 返回协议关键字
     * @call 当子类需要用协议时候调用
     */
    public abstract String getInterfaceKey();
}
